#ifndef _FUNCTIONS_H_
#define _FUNCTIONS_H_

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include "ListaDinamica.h"
#include "MathLib.h"

#define BLOCKSIZE 65535     /* tamanho do bloco que vai ser lido a cada iteracao do fread */
#define FILESIZE 10000000      /* tamanho estimado do arquivo */
#define CODESIZE 20


typedef struct {
    char ascii;     /* ascii do elemento do vetor de diferencas */
    char binary[9]; /* correspondente binario do ascii acima */
    unsigned short int nbits;   /* numero de bits necessarios para representar o ascii do elemento */
} TypeDiff;

typedef struct {
    TypeDiff diff_elem;       /* elemento do vetor de diferencas */
    char huffman_code[8];      /* codigo de huffman para o elemento */
    char str_code[CODESIZE];      /* codigo final para escrita no arquivo */
} DiffCode;

typedef struct {
    char *buffer;   /* vetor que aloca todo o conteudo do arquivo */
    DiffCode *dcode;
    unsigned long int size;     /* quantidade total de caracteres ascii do arquivo */
    HuffTree huffroot;
} FileStruct;

/* Le todo o arquivo e aloca no vetor buffer */
FileStruct *ReadFile(FileStruct *file, char *filename) {
    size_t nbytes, count = 0;
    char *aux;
    FILE *fd;

    file = malloc(sizeof(FileStruct));
    aux = malloc((BLOCKSIZE+1)*sizeof(char));
    file->buffer = malloc(FILESIZE*sizeof(char));
    fd = fopen(filename, "rb");     /* abre o arquivo no modo binario e leitura */
     if(fd == NULL) {
        printf("The file wasn't opened!\n");
        exit(1);
    }
    while((nbytes = fread(aux, 1, BLOCKSIZE, fd)) > 0) {    /* le o conteudo do arquivo */
        count += nbytes;
        strncat(file->buffer, aux, nbytes);
    }
    file->size = count;     /* armazena a quantidade total de caracteres */
    printf("The file has %ld bytes!\n", file->size);
    printf("Tamanho texto: %ld\n", strlen(file->buffer)); 
    fclose(fd);

    return file;
}

/*Cria efetivamente o codigo de huffman*/
/*level indica a profundidade que estou. Na raiz level = 0.
Os filhos dela 1 e assim por diante*/
void RecursiveGenerateCode(HuffTree root, char *aux, int level, char **code){
	if(root != NULL){
		/*O codigo de huffman eh atribuido quando se chega a folha*/
		if(IsLeaf(root)){
			aux[level] = '\0';
			printf("****************\nFreq: %d\n", root->frequency);
			printf("Symbol: %d\n", root->nbits);
			printf("huffcode: %s\n****************\n", aux);

			code[root->nbits] = (char*) malloc(sizeof(char)*(strlen(aux)+1));
			strcpy(code[root->nbits], aux);
		}/*fim if*/

		/*caminhar para a esquerda code=1*/
		aux[level] = '1';
		RecursiveGenerateCode(GetLeft(root), aux, level+1, code);

		/*caminhar para a direita code=0*/
		aux[level] = '0';
		RecursiveGenerateCode(GetRight(root), aux, level+1, code);

	}/*fim if*/
}

/* Gera o codigo de huffman de acordo com a arvore de huffman */
char **GenerateHuffCode(FileStruct *file) {
	char *aux = NULL;
	char **code = NULL;
	int i;

	code = (char**) calloc(8, sizeof(char*) );
	aux = (char*) calloc(9, sizeof(char));
	RecursiveGenerateCode(file->huffroot, aux, 0, code);
	free(aux);

	return code;
}

/* Calcula o vetor de diferencas */
void CreateDiffCode(FileStruct *file) {
    unsigned register int i;

    file->dcode = malloc(file->size*sizeof(DiffCode));
    file->dcode[0].diff_elem.ascii = toascii(file->buffer[i]);
    strcpy(file->dcode[i].diff_elem.binary, decToBin(file->dcode[i].diff_elem.ascii));
	file->dcode[0].diff_elem.nbits = numberOfBits(file->dcode[i].diff_elem.ascii);
    for(i = 1; i < file->size; i++) {
        file->dcode[i].diff_elem.ascii = toascii(file->buffer[i]) - toascii(file->buffer[i-1]);
        if(file->dcode[i].diff_elem.ascii == 0) {
            file->dcode[i].diff_elem.nbits = 0;
        } else {
			file->dcode[i].diff_elem.nbits = numberOfBits(file->dcode[i].diff_elem.ascii);
        }
        strcpy(file->dcode[i].diff_elem.binary, decToBin(file->dcode[i].diff_elem.ascii));
    }

}

/* Cria a arvore de Huffman com base no vetor de diferencas  */
char **HuffmanTree(FileStruct *file) {
    unsigned register int i;
    HuffTree auxl, auxr, *huffcode;    /* ponteiros para as subarvores esquerda e direita */
    TipoLista lista;    /* TAD para executar o algoritmo de Huffman */
    /* count: indice que controla o no que sera usado
    pos: indice para remover um no da lista
    freql e freqr: frequencias do no esquerdo e no direito */
    int count, pos, freql, freqr;
    short int nbits;
    unsigned long int *freq_bits = calloc(8, sizeof(unsigned long int));
    char **code = NULL;

    CreateDiffCode(file);    /* calcula o vetor de diferencas e armazena em file->diff */
    for(i = 0; i < file->size; i++) {
        nbits = file->dcode[i].diff_elem.nbits;
        freq_bits[nbits]++;
    }
    huffcode = malloc((2*8-1)*sizeof(HuffTree));    /* aloca um conjunto de nos para criar a arvore */
    lista = IniciaLista();
    for(i = 0; i < 8; i++) {  /* para cada no com frequencia definida (nos folhas), a frequencia e o numero de bits sao setados */
        if(freq_bits[i] > 0) {
            huffcode[i] = NewTree();
            SetFrequency(huffcode[i], freq_bits[i]);
            SetSymbol(huffcode[i], i);
            InsereUltimoLista(lista, huffcode[i]);
        }
    }
    count = i;

    do {    /* algoritmo de Huffman. Ao final desse loop, tem-se o no raiz da arvore de huffman */
        huffcode[count] = NewTree();
        auxl = PesquisaMinimo(lista, &pos);
        RemoveDeLista(lista, pos);
        auxr = PesquisaMinimo(lista, &pos);
        RemoveDeLista(lista, pos);
        freql = GetFrequency(auxl);
        freqr = GetFrequency(auxr);
        SetFrequency(huffcode[count], freql+freqr);
        SetLeft(huffcode[count], auxl);
        SetRight(huffcode[count], auxr);
        InsereUltimoLista(lista, huffcode[count]);
        count++;
    } while(TamanhoLista(lista) > 1);

    count--;
    DestroiLista(lista);
    file->huffroot = huffcode[count];
	/*PrintTree(huffcode[count]); */
    code = GenerateHuffCode(file);

    return code;

}

/*
Cria e escreve no arquivo *file, o codigo *code e os valores da arvore de huffman **codeHuffman*/
void WriteFileCode(char* code, char *filein, char **codeHuffman){
	unsigned int size, i, flag = 0, pos = 0, count = 0;
	char remainder[2], c;
	unsigned char *aux = NULL;
	unsigned char *aux2 = NULL;
	unsigned char *auxHuff = NULL;
	unsigned char aux3[6];
	FILE *fp = NULL;
	char *fileout = (char*)malloc((strlen(filein)+5)*sizeof(char));

	strcpy(fileout, filein);
	strcat(fileout, ".bin");
	/*cria um novo arquivo para a escrita*/
	fp = fopen(fileout, "wb");
	if(fp == NULL){ fprintf(stderr, "Nao foi possivel criar o arquivo final\n"); exit(1); }
	
	/********************cria o cabecalho do arquivo*****************/
	
	/*1)Coloca o tamanho de bits uteis no ultimo byte*/
	size = (unsigned int) strlen(code);
	sprintf(remainder, "%d", size % 8); /*para saber quanto bits ignorar no ultimo byte*/
	fwrite(remainder, sizeof(char), 1, fp);/*escreve no arquivo essa quantidade*/
	
	/*2)Coloca a arvore de huffman na arvore*/
	
	auxHuff = (char*)calloc(1, sizeof(char));
	auxHuff[0] = '\0';
	
	/*cada codigo de huffman eh separado por ';'*/
	for(i = 0; i < 8; i++){
		if(codeHuffman[i] != NULL){/*verifica se existe o codigo de huffman*/
			sprintf(aux3, "%d;", binToDec(codeHuffman[i]));/*Escreve o valor decimal do codigo de huffman*/
			auxHuff = realloc(auxHuff, sizeof(char)*(strlen(auxHuff)+strlen(aux3)+1));
			strcat(auxHuff, aux3);
		}/*fim if NULL*/
		else{/*Se nao existir o codigo para a qtdade i de bits, coloca-se apenas o separador ';' */
			auxHuff = realloc(auxHuff, sizeof(char)*(strlen(auxHuff)+2));
			strcat(auxHuff, ";");
		}/*fim else*/
	}/*fim for*/
	
	fwrite(auxHuff, sizeof(char), strlen(auxHuff), fp);
	
	/*****************fim do cabecalho**************************/
	
	/*iniciando as variaveis*/
	aux = (unsigned char*) calloc(5120, sizeof(char));/*Escrever de 100 em 100 bytes no arquivo*/
	aux2 = (unsigned char*) calloc(9, sizeof(char)); /**/
	aux2[8] = '\0';
	
	do{
		for(i = 0; (i < 5120) && (!flag); i++){
			/*copia de 8 em 8 bits para a aux2*/
			strncpy(aux2, &code[pos], 8);
		/*transforma o valor binario pra decimal e armazena num unsigned char(0-255)*/
			aux[i] = (unsigned char) binToDecPure(aux2);
			pos = pos + 8; /*reposiciona para ler os proximos 8 bits*/
			if(pos > size) flag = 1; /*verifica se acabou a string*/
		}/*fim for*/

		aux[i] = '\0';
		if(!flag)
			fwrite(aux, sizeof(char), 5120, fp); /*escreve 100 bytes no arquivo*/
		else
			fwrite(aux, sizeof(char), i, fp);
	}while(pos < size);

	/*fecha arquivo e libera a memoria*/
	fclose(fp);
	free(aux);
	free(aux2);
	free(auxHuff);
	
}/*fim WriteFileCode*/

char *GenerateFinalCode(char **code, FileStruct *file) {
    unsigned register int i;
    unsigned short int nbits;
    char *finalcode = malloc(file->size*CODESIZE*sizeof(char));

    for(i = 0; i < file->size; i++) {
        nbits = file->dcode[i].diff_elem.nbits;
        strcpy(file->dcode[i].huffman_code, code[nbits]);
        strcpy(file->dcode[i].str_code, file->dcode[i].huffman_code);
        if(nbits != 0) {
            strcat(file->dcode[i].str_code, file->dcode[i].diff_elem.binary);
        }
        strcat(finalcode, file->dcode[i].str_code);
    }

    return finalcode;
}

/*
Le arquivo file. Retorna um char* com o codigo em binario e **code contem os valores da arvore de huffman */
char *ReadFileCode(char *file, char **codeHuffman){
	FILE *fp = NULL;
	int i, amount;
	char ch[2], aux3[5];
	unsigned char *code = NULL;
	unsigned char *aux = NULL;
	unsigned char *aux2 = NULL;
	int remainder;

	/*Le arquivo com permissao de leitura*/
	fp = fopen(file, "rb");
	if(fp == NULL){ fprintf(stderr, "%s","Nao foi possivel ler arquivo\n"); return NULL; }
	
	/********************le o cabecalho************************************/
	/*le tamanho de bits uteis no ultimo byte*/
	ch[1] = '\0';
	fread(ch, sizeof(char), 1, fp); /*Valor de remainder contida no arquivo*/
	remainder = atoi(ch);

	/*le a arvore de huffman*/	
	for(i = 0; i < 8; i++){
		aux3[0] = '\0';
		
		/*Le ate encontrar um ';'*/
		do{
			fread(ch, sizeof(char), 1, fp);
			strcat(aux3, ch);

		}while(ch[0] != ';');

		/*verifica-se tinha um numero*/
		if(aux3[0] != ';'){
			/*pega o codigo de huffman*/
			codeHuffman[i] = (char*) malloc(sizeof(char)*strlen(aux3));
			aux2 = decToBin(atoi(aux3));
			strcpy(codeHuffman[i], aux2);
			free(aux2);
		}/*fim if*/
	}/*fim for*/
	
	/********************termina de ler o cabecalho************************/


	code = (char*) malloc(sizeof(char));
	code[0] = '\0';
	aux = (char*) malloc(sizeof(char) * 101);

	do{
		
		amount = fread(aux, sizeof(char), 100, fp); /*le de 100 em 100 do arquivo*/
		aux[amount] = '\0';
		for(i = 0; i < amount; i++){
			aux2 = decToBinPure((int)aux[i]);/*valor binario do decimal lido*/
			code = (char*) realloc(code, (strlen(code) + strlen(aux2) + 1)*sizeof(char));/*realoca o tamanho da string para concatenacao*/
			strcat(code, aux2);/*cocatena o code atua com o lido*/
			free(aux2);
		}/*fim for*/

	}while(!feof(fp));
	fclose(fp);
	
	/*Verificando a quantidade de bits para ignorar*/
	if(remainder != 0){
		char a;
		int size = strlen(code), j = size - 8;

		/*Faz o deslocamento de bits. Ex: para remainder=5 00001011 -> 01011000*/
		for(i = (size-remainder); i < size; i++)
			code[j++] = code[i];

		/*realoca com o tamanho correto, ignorando os bits*/
		code = (char*) realloc(code, sizeof(char)*(size-(9-remainder)));
		code[size-(8-remainder)] = '\0';
	}/*fim if remainder*/
	free(aux);
	return code;

}/*fim ReadFileCode*/

void DecodeFile(char *filecode, char **codeHuffman) {
	unsigned register int i;
	char *codeaux = NULL, *p = NULL, *p2 = NULL;
	int max_bits=0, nbits, count=0;
	char diff_ascii=0;
	char *decoded = (char*)malloc(1000000*sizeof(char));
	FILE *arq = NULL;

	arq = fopen("decoded.txt", "wb");
	if(arq == NULL) {
		printf("Cannot open the file to write!\n");
		exit(1);
	}

	for(i = 0; i < 8; i++) {
		nbits = strlen(codeHuffman[i]);
		if(nbits > max_bits)
			max_bits = nbits;	
	}		
	p = filecode;
	codeaux = (char*)malloc((max_bits+1)*sizeof(char));
	do{	
		strncpy(codeaux, p, max_bits);
		for(i = 0; i < 8; i++) {
			nbits = strlen(codeHuffman[i]);
			if(strncmp(codeaux, codeHuffman[i], nbits) == 0)
				break;
		}
		p += nbits;
		if(i > 0) {
			strncpy(codeaux, p, i);
			codeaux[i] = '\0';
			diff_ascii += binToDec(codeaux);
		}
		decoded[count++] = diff_ascii;
		p += i;
	} while(p[0] != '\0');

	decoded[count] = '\0';
	/*for(i = 0; i < count; i++)
		printf("%d ", decoded[i]);*/
	
	fwrite(decoded, 1, strlen(decoded), arq);

	fclose(arq); 

}



#endif
